/*=============================================================================
  TextureStreaming.cpp : Common Texture Streaming manager implementation.
  Copyright (c) 2001 Crytek Studios. All Rights Reserved.

  Revision history:
										* Created by Honich Andrey
- 19:8:2008   12:14 : Refactored by Anton Kaplanyan

=============================================================================*/


#include "StdAfx.h"
#include "../CommonRender.h"
#include "Image/DDSImage.h"
#include "StringUtils.h"								// stristr()
#include "ILocalMemoryUsage.h"
#include <IJobManager_JobDelegator.h>

// checks for MT-safety of called functions
#define CHK_RENDTH assert(gRenDev->m_pRT->IsRenderThread())
#define CHK_MAINTH assert(gRenDev->m_pRT->IsMainThread())
#define CHK_MAINORRENDTH assert(gRenDev->m_pRT->IsMainThread() || gRenDev->m_pRT->IsRenderThread())

DECLARE_JOB("Texture_ExpandMipFromFile2", TTexture_ExpandMipFromFileJob2, CTexture::ExpandMipFromFile );

int CTexture::s_nStreamTimeSlicingTexID = 0;
int CTexture::s_bPoolOverflow = 0;
int CTexture::s_bPoolOverflowTotally = 0;
bool CTexture::s_bStreamingFromHDD = true;
std::vector<CTexture *> CTexture::s_StreamingRequested;
std::vector<CTexture *> CTexture::s_DistanceSortedTextures;
CryMT::queue<IReadStreamPtr> CTexture::s_StreamingTextures;
volatile TIntAtomic CTexture::s_nBytesSubmittedToStreaming = {0};
int CTexture::s_nBytesRequiredNotSubmitted = 0;

#if !defined (_RELEASE) || defined(ENABLE_STATOSCOPE_RELEASE)
int CTexture::s_TextureUpdates = 0;
float CTexture::s_TextureUpdatesTime = 0.0f;
int CTexture::s_TexturesUpdatedRendered = 0;
float CTexture::s_TextureUpdatedRenderedTime = 0.0f;
int CTexture::s_StreamingRequestsCount = 0;
float CTexture::s_StreamingRequestsTime = 0.0f;
#endif

bool CTexture::s_bStreamDontKeepSystem = false;

int CTexture::s_nTexturesDataBytesLoaded = 0;
volatile int CTexture::s_nTexturesDataBytesUploaded = 0;
volatile TIntAtomic CTexture::s_nStatsCurManagedStreamedSysTexMem = {0};
volatile TIntAtomic CTexture::s_nStatsCurManagedStreamedTexMem = {0};
volatile TIntAtomic CTexture::s_nStatsCurManagedNonStreamedTexMem = {0};
volatile TIntAtomic CTexture::s_nStatsCurDynamicTexMem = {0};

STexStreamAsyncSharedInfo::~STexStreamAsyncSharedInfo()
{
	// if we have streaming error, release new pool item
	if(m_pNewPoolItem)
	{
		m_pNewPoolItem->m_pTex = NULL;
		m_pNewPoolItem->Unlink();
		m_pNewPoolItem->LinkFree(&CTexture::s_FreeTexPoolItems);
		m_pNewPoolItem = NULL;
    m_pTexture = 0;
	}
}

STexAsyncUnloadInfo::~STexAsyncUnloadInfo()
{
	// if we have streaming error, release new pool item
	if(m_pNewPoolItem)
	{
		m_pNewPoolItem->m_pTex = NULL;
		m_pNewPoolItem->Unlink();
		m_pNewPoolItem->LinkFree(&CTexture::s_FreeTexPoolItems);
		m_pNewPoolItem = NULL;
    m_pTexture = 0;
	}
}

void CTexture::StreamReleaseMipsData(int nStartMip, int nEndMip)
{
	assert(m_pFileTexMips);
	assert(nStartMip <= nEndMip);
	nEndMip = min(nEndMip, m_nMips - 1);
	nStartMip = min(nStartMip, nEndMip);
	const int nSides = (m_eTT == eTT_Cube) ? 6 : 1;
	for (int i=0; i<nSides; i++)
    for (int j=nStartMip; j<=nEndMip; j++)
      m_pFileTexMips->m_pMipHeader[j].m_Mips[i].Free();
}

int CTexture::StreamUnload(const bool bDestroy)
{
	CHK_RENDTH;

	if (IsUnloaded() || !IsStreamed() || !CRenderer::CV_r_texturesstreaming)
    return 0;

	if(m_bStreamingInProgress)
		AbortStreamingTasks(this);
	assert(!m_bStreamingInProgress);
	m_pFileTexMips->m_nStreamingSwitchUpdateId = m_pFileTexMips->m_arrSPInfo[MAX_PREDICTION_ZONES-1].nRoundUpdateId;
  int nDevSize = m_nActualSize;





	// copy old mips to system memory
	StreamCopyMipsTexToMem(m_nMips - m_CacheFileHeader.m_nMipsPersistent, m_nMips - 1, false, NULL);
  if (bDestroy)
    ReleaseDeviceTexture(true);
  else if (m_pFileTexMips->m_pPoolItem)
    StreamRemoveFromPool();
  SetWasUnload(true);
#ifndef _RELEASE
	if (CRenderer::CV_r_TexturesStreamingDebug == 2)
		iLog->Log("Unloading unused texture: %s", m_SrcName.c_str());
#endif
  return nDevSize;
}


















































































































































































// streaming thread
void CTextureStreamCallback::StreamAsyncOnComplete (IReadStream* pStream, unsigned nError)
{
	STexAsyncStreamingInfo *pTexStreamInfo = (STexAsyncStreamingInfo *)pStream->GetUserData();
	STexStreamAsyncSharedInfo *pTexStreamSharedInfo = pTexStreamInfo->m_pSharedInfo;
	assert(pTexStreamSharedInfo->m_nAsyncRefCount > 0);
	CryInterlockedDecrement(&pTexStreamSharedInfo->m_nAsyncRefCount);

	if(nError)
	{
		if(nError != ERROR_USER_ABORT)
			gEnv->pLog->LogError("Error streaming texture: Error '%s', Texture: %s", pStream->GetErrorName(), pStream->GetName());
		pStream->FreeTemporaryMemory(); // We don't need internal stream loaded buffer anymore.
		return;
	}

	CTexture *tp = pTexStreamSharedInfo->m_pTexture; 
	assert(tp); 
	assert(pTexStreamSharedInfo->m_nHigherUploadedMip < tp->m_nMinMipVidUploaded);   
	
	if(tp->m_pFileTexMips == NULL)
	{
		assert(0);
		pStream->FreeTemporaryMemory(); // We don't need internal stream loaded buffer anymore.
		return;
	}

	// download data
	const byte *Src = (byte *)pStream->GetBuffer();
	STexCacheFileHeader *fh = &tp->m_CacheFileHeader;
	const int i = pTexStreamInfo->m_nRelativeMipLevel + pTexStreamSharedInfo->m_nHigherUploadedMip;
	STexStreamingInfo::SMipHeader& mh = tp->m_pFileTexMips->m_pMipHeader[i];
	const int SrcSideSize = CTexture::TextureDataSize(mh.m_USize, mh.m_VSize, 1, 1, tp->GetSrcFormat());
	const int32 nSides = tp->GetTexType() != eTT_Cube ? 1 : 6;
	assert(SrcSideSize * nSides == pStream->GetParams().nSize);

	for(int32 iSide = 0;iSide < nSides;++iSide)
	{
		SMipData *mp = &mh.m_Mips[iSide];
		if(!mp->DataArray)
			mp->Init(mh.m_SideSize, mh.m_USize, mh.m_VSize);
		if( !gRenDev->m_pRT->IsMainThread() && !gRenDev->m_pRT->IsRenderThread() )
		{							
			TTexture_ExpandMipFromFileJob2 job(&mp->DataArray[0], mh.m_SideSize, Src, SrcSideSize, tp->GetSrcFormat() );	
			job.SetCacheMode(JobManager::SPUBackend::eCM_4); 					
			job.SetJobType(JobManager::SPUBackend::eOffLoadJob);			
			job.Run();			
		}
		else
		{		
			CTexture::ExpandMipFromFile(&mp->DataArray[0], mh.m_SideSize, Src, SrcSideSize, tp->GetSrcFormat());
		}
		Src += SrcSideSize;
	}

	pStream->FreeTemporaryMemory(); // We don't need internal stream loaded buffer anymore.

	// collect statistics
#ifndef _RELEASE
	if(pStream->GetParams().nSize >= 1024)
		CTexture::s_nStreamingThroughput += pStream->GetParams().nSize;
#endif
}

// main thread
void CTextureStreamCallback::StreamOnComplete (IReadStream* pStream, unsigned nError)
{
	STexAsyncStreamingInfo *pTexStreamInfo = (STexAsyncStreamingInfo *)pStream->GetUserData();
	STexStreamAsyncSharedInfo *pTexStreamSharedInfo = pTexStreamInfo->m_pSharedInfo;

	CTexture *tp = pTexStreamSharedInfo->m_pTexture;

	CryInterlockedAdd(CTexture::s_nBytesSubmittedToStreaming.Addr(), -(int)pStream->GetParams().nSize);
	assert(CTexture::s_nBytesSubmittedToStreaming >= 0);

	// check if we have loaded all chunks
	if((--pTexStreamSharedInfo->m_nSyncRefCount) > 0)
	{
		CTexture::StreamRemoveTask(tp, IReadStreamPtr(pStream));
		SAFE_DELETE(pTexStreamInfo);
		return;
	}

  if(nError || !tp)
  {
		if(nError != ERROR_USER_ABORT)
			gEnv->pLog->LogError("Error streaming texture: Error '%s', Texture: %s", pStream->GetErrorName(), pStream->GetName());
		CTexture::StreamRemoveTask(tp, IReadStreamPtr(pStream));
		if(tp) tp->SetStreamingInProgress(false);
		SAFE_DELETE(pTexStreamInfo);
		SAFE_DELETE(pTexStreamSharedInfo);
		return;
  }

	assert(pTexStreamSharedInfo->m_nSyncRefCount == 0);
	assert(pTexStreamSharedInfo->m_nAsyncRefCount == 0);

	// add reference
	pStream->AddRef();
	tp->AddRef();

  gRenDev->m_pRT->RC_TexStreamComplete(pStream);
}

// render thread
void CTexture::StreamOnComplete (IReadStream* pStream)
{
	CHK_RENDTH;
	PROFILE_FRAME(Texture_StreamComplete);

	if (!pStream)
	{
		assert(0);
		return;
	}

	STexAsyncStreamingInfo *pTexStreamInfo = (STexAsyncStreamingInfo *)pStream->GetUserData();
	assert(pTexStreamInfo);
	if (!pTexStreamInfo)
	{
		assert(0);
		pStream->Release();
		return;
	}
	STexStreamAsyncSharedInfo *pTexStreamSharedInfo = pTexStreamInfo->m_pSharedInfo;
	assert(pTexStreamSharedInfo && pTexStreamSharedInfo->m_nSyncRefCount == 0);

  CTexture *tp = pTexStreamSharedInfo->m_pTexture;
  if (pStream->IsError() || !tp || !tp->m_pFileTexMips)
  {
		if (tp && !tp->m_pFileTexMips)
		{
			assert(0);
			gEnv->pLog->LogWarning("CTextureStreamCallback::StreamOnComplete texture is corrupted %s", pStream->GetName());
		}
		if(pStream->GetError() != ERROR_USER_ABORT)
			CTexture::StreamRemoveTask(NULL, IReadStreamPtr(pStream));
		SAFE_DELETE(pTexStreamInfo);
		SAFE_DELETE(pTexStreamSharedInfo);
		if(tp) tp->SetStreamingInProgress(false);
		pStream->Release();
		// dec ref
		if(tp) tp->Release();
		return;
  }

#ifdef DO_RENDERLOG
  if (gRenDev->m_LogFileStr)
    gRenDev->LogStrv(SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID], 
											"Async Finish Load '%s' (Mips: %d-%d[%d]), Size: %d, Time: %.3f\n", 
											tp->GetSourceName(), pTexStreamSharedInfo->m_nHigherUploadedMip, pTexStreamSharedInfo->m_nHigherUploadedMip + pTexStreamInfo->m_nRelativeMipLevel, 
											tp->m_nMips, pStream->GetParams().nSize, iTimer->GetAsyncCurTime());
#endif

	assert(tp->m_nMinMipVidUploaded - 1 == pTexStreamSharedInfo->m_nLowerUploadedMip);
  tp->StreamUploadMips(pTexStreamSharedInfo->m_nHigherUploadedMip, tp->m_nMinMipVidUploaded - 1, pTexStreamSharedInfo->m_pNewPoolItem);
  if (!pTexStreamSharedInfo->m_nHigherUploadedMip || s_bStreamDontKeepSystem)
    tp->StreamReleaseMipsData(0, tp->m_nMips - 1);

	// collect statistics
#if !defined(_RELEASE) || defined(ENABLE_STATOSCOPE_RELEASE)
	const CTimeValue currentTime = iTimer->GetAsyncTime();
	if(currentTime - pTexStreamSharedInfo->m_fStartTime >  .01f)	// avoid measurement errors for small textures
		CTexture::s_nStreamingTotalTime += (currentTime - pTexStreamSharedInfo->m_fStartTime).GetSeconds();
#endif

	if(pStream->GetError() != ERROR_USER_ABORT)
		CTexture::StreamRemoveTask(tp, IReadStreamPtr(pStream));

	if(CRenderer::CV_r_texturesstreamingmipfading)
		tp->m_fCurrentMipBias = min(2.f, tp->m_fCurrentMipBias + float(pTexStreamSharedInfo->m_nLowerUploadedMip - pTexStreamSharedInfo->m_nHigherUploadedMip + 1));

	tp->SetStreamingInProgress(false);
  if (tp->GetDevTexture())
    tp->SetWasUnload(false);

	// dec ref
	pStream->Release();
	tp->Release();

	// release data
	SAFE_DELETE(pTexStreamInfo);
	SAFE_DELETE(pTexStreamSharedInfo);
}


void CTexture::StreamLoadSynchronously(int nStartMip, int nEndMip)
{
	CHK_RENDTH;
	int iMip, iSide;
  int nMips, nSides;
  int nOffsSide = 0;
  STexCacheFileHeader *fh = &m_CacheFileHeader;
  nSides = fh->m_nSides;
  nMips = m_nMips;

  if (nStartMip < 0)
		return;

	// we don't need to load mips that we already loaded
	nEndMip = min(m_nMinMipVidUploaded-1, nEndMip);
	if(nStartMip > nEndMip)
		return;

	// check if it fits to pool
	if(IsStreamed() && m_bStreamPrepared)
	{
		const int nSizeToLoad = (m_pFileTexMips->m_pMipHeader[nStartMip].m_SideSizeWithMips - m_pFileTexMips->m_pMipHeader[nEndMip].m_SideSizeWithMips) * nSides / 1024;
		const int nCurPoolSize = CTexture::s_nStatsCurManagedStreamedTexMem / 1024;
    const int nMaxPoolSize = CRenderer::GetTexturesstreampoolsize()*1024;
		if(nCurPoolSize + nSizeToLoad >= nMaxPoolSize)
		{
			// trigger pool overflow
			s_bPoolOverflow = 1;
			return;
		}
	}

	PROFILE_FRAME(Texture_LoadSynchronously);

	const int32 nSideSize = m_nFullSize/nSides;

  assert (nSideSize == m_pFileTexMips->m_pMipHeader[0].m_SideSizeWithMips);

  int SizeToLoad = 0;
  for (iMip=nStartMip; iMip<=nEndMip;++iMip)
  {
		STexStreamingInfo::SMipHeader& mh = m_pFileTexMips->m_pMipHeader[iMip];

		size_t sizeToRead = CTexture::TextureDataSize(mh.m_USize, mh.m_VSize, max(1, m_nDepth>>iMip), 1, GetSrcFormat());
		if((m_nFlags & FT_REPLICATE_TO_ALL_SIDES) == 0)
			sizeToRead *= nSides;

		byte* pSides = NULL;
		std::vector<byte> vecSides;
		if(nSides > 1 && !(m_nFlags & FT_REPLICATE_TO_ALL_SIDES))
		{
			vecSides.resize(mh.m_SideSize * nSides);
			pSides = &vecSides[0];
		}

		for (iSide=0; iSide<nSides;++iSide)
		{
			SMipData *mp = &mh.m_Mips[iSide];
			if (mp->DataArray)
        continue;
			mp->Init(mh.m_SideSize, mh.m_USize, mh.m_VSize);
			assert(mp->DataArray);
			if(!pSides)
				pSides = &mp->DataArray[0];
      CTexture::s_nTexturesDataBytesLoaded += mh.m_SideSize;
      SizeToLoad += mh.m_SideSize;

			// load chunk from file (all sides at once)
			if (iSide == 0)
			{
				uint32 nImageFlags = 0;
				if(m_nFlags & FT_ALPHA)			nImageFlags |= FIM_ALPHA;
				if(m_nFlags & FT_SPLITTED)	nImageFlags |= FIM_SPLITTED;
				size_t readSize = DDSSplitted::LoadMips( m_SrcName, pSides, m_pFileTexMips->m_pMipHeader[iMip].m_Mips[0].m_Seek, sizeToRead, 
																									m_nWidth, m_nHeight, m_nDepth, nSides,
																									m_nMips, iMip, iMip, m_CacheFileHeader.m_nMipsPersistent, GetSrcFormat(), nImageFlags);
				assert(readSize == sizeToRead);
			}

			if(iSide == 0 || !(m_nFlags & FT_REPLICATE_TO_ALL_SIDES))
			{
				size_t nSrcDataSideSize = CTexture::TextureDataSize(mh.m_USize, mh.m_VSize, max(1, m_nDepth>>iMip), 1, m_eTFSrc);
				CTexture::ExpandMipFromFile(&mp->DataArray[0], mh.m_SideSize, pSides + iSide * nSrcDataSideSize, nSrcDataSideSize, m_eTFSrc);
			}

      if (GetSrcFormat() == eTF_R8G8B8)
      {
        for (int iTexel=mh.m_USize*mh.m_VSize-1; iTexel >= 0; --iTexel)
        {
#ifndef XENON
					ColorB texel(mp->DataArray[iTexel*3+0], mp->DataArray[iTexel*3+1], mp->DataArray[iTexel*3+2], 255);


#endif
					(ColorB&)mp->DataArray[iTexel*4] = texel;
        }
      }
      else if(iSide > 0)
      {
        if (m_nFlags & FT_REPLICATE_TO_ALL_SIDES)
          memcpy(&mp->DataArray[0], &mh.m_Mips[0].DataArray[0], mh.m_SideSize);
      }
    }
#ifdef DO_RENDERLOG
    if (gRenDev->m_LogFileStr && SizeToLoad > 0)
      gRenDev->LogStrv(SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID], "Sync Load '%s' (Side: %d, %d-%d[%d]), Size: %d, Time: %.3f\n", m_SrcName.c_str(), iSide, nStartMip, iMip-1, nMips, SizeToLoad, iTimer->GetAsyncCurTime());
#endif
    nOffsSide += nSideSize;
  }
  { 
    PROFILE_FRAME(Texture_LoadFromCache_UploadSync);















		StreamUploadMips(nStartMip, nMips-1, NULL);

		if(s_bStreamDontKeepSystem)
			StreamReleaseMipsData(0, m_nMips - 1);
  }
	m_fCurrentMipBias = 0.f;
	SetWasUnload(false);
}

int CTexture::StreamCalculateMipsSigned(float fMipFactor) const
{
	assert(IsStreamed());
	const uint32 nMaxExtent = max(m_nWidth, m_nHeight);
	float currentMipFactor = fMipFactor * nMaxExtent * nMaxExtent * gRenDev->GetMipDistFactor();
	int nMip = int(floorf(0.5f * logf(max(currentMipFactor, 0.1f)) / LN2 + (CRenderer::CV_r_TexturesStreamingMipBias + gRenDev->m_fTexturesStreamingGlobalMipFactor)));
	const int nNewMip = min(nMip, m_nMips - m_CacheFileHeader.m_nMipsPersistent);
	return nNewMip;
}

int CTexture::GetStreamableMipNumber() const
{
	assert(IsStreamed());
	return max(0, m_nMips - m_CacheFileHeader.m_nMipsPersistent);
}

int CTexture::GetStreamableMemoryUsage(int nStartMip) const
{
	assert(IsStreamed());
	if (m_pFileTexMips == NULL)
	{
		assert(0);
		return 0;
	}

	return m_pFileTexMips->m_pMipHeader[nStartMip].m_SideSizeWithMips;
}

void CTexture::StreamUpdateMip(const float fMipFactor, const int nFlags, const int nUpdateId, const int nCounter)
{
	CHK_MAINORRENDTH;
	
	assert(fMipFactor >= 0.f);

	int nZoneId = (nFlags & FPR_SINGLE_FRAME_PRIORITY_UPDATE) ? 0 : 1;

	STexStreamingInfo::SStreamPrioriryInfo& rInfo = m_pFileTexMips->m_arrSPInfo[nZoneId];

	const int nCurrentRoundUpdateId = gRenDev->m_RP.m_TI[gRenDev->m_pRT->GetThreadList()].m_arrZonesRoundId[nZoneId];

	if (rInfo.nRoundUpdateId != nUpdateId)
	{
		STATIC_CHECK(MAX_PREDICTION_ZONES == 2, THIS_CODE_IS_OPTIMISED_ASSUMING_THIS_EQUALS_2);

		// reset mip factor and save the accumulated value into history and compute final mip factor
		float fFinalMipFactor = fMipFactor;
		if(rInfo.nRoundUpdateId>=0 && (rInfo.nRoundUpdateId > gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_arrZonesRoundId[nZoneId]-2))
			fFinalMipFactor = min(fFinalMipFactor, rInfo.fMinMipFactor);
			
		// if the min mip factor is at its default value, initialise the entire history with the new mip factor
		rInfo.fLastMinMipFactor = (float)__fsel(rInfo.fMinMipFactor - 1000000.0f, fMipFactor, rInfo.fMinMipFactor);
		rInfo.fMinMipFactor = fMipFactor;

		// reset the high prio flags
		rInfo.bLastHighPriority = rInfo.bHighPriority; 
		rInfo.bHighPriority = false;

		// update round id
		rInfo.nRoundUpdateId = max(nUpdateId, nCurrentRoundUpdateId);
		
		if(nZoneId == (MAX_PREDICTION_ZONES-1))
		{
			m_pFileTexMips->m_nLastPrioriryUpdateCounter = m_pFileTexMips->m_nPrioriryUpdateCounter;
			m_pFileTexMips->m_nPrioriryUpdateCounter = 0;
		}

		// consider the alternate zone mip factor as well
		nZoneId ^= 1;
		STexStreamingInfo::SStreamPrioriryInfo& rOtherInfo = m_pFileTexMips->m_arrSPInfo[nZoneId];
		if(rOtherInfo.nRoundUpdateId>=0 && (rOtherInfo.nRoundUpdateId > gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_arrZonesRoundId[nZoneId]-2))
			fFinalMipFactor = min(fFinalMipFactor, rOtherInfo.fLastMinMipFactor);

		CheckEnqueueForStreaming(fFinalMipFactor, rInfo.bLastHighPriority);
	}

	if(nZoneId == (MAX_PREDICTION_ZONES-1))
		m_pFileTexMips->m_nPrioriryUpdateCounter += nCounter;

	rInfo.fMinMipFactor = min(rInfo.fMinMipFactor, fMipFactor);
	rInfo.bHighPriority = (rInfo.bHighPriority || nFlags & FPR_HIGHPRIORITY);
#if !defined (_RELEASE)
	s_TextureUpdates += 1;
#endif 
}

void CTexture::CheckEnqueueForStreaming(const float fMipFactor, bool bHighPriority)
{
	// calculate the new lod value
	int nMipIdSigned = CTexture::StreamCalculateMipsSigned(fMipFactor);
	int nMipClamp = (s_bStreamingFromHDD || bHighPriority) ? 0 : CRenderer::CV_r_TexturesStreamingMipClampDVD;
	const int nNewMip = max(nMipClamp, nMipIdSigned);

	if ( CRenderer::CV_r_texturesstreamingResidencyEnabled == 1 && nNewMip != m_pFileTexMips->m_nMinMipCur && !(m_pFileTexMips->m_eStreamingInfoFlags&STexStreamingInfo::eStreamingInfo_Resident))
	{
		const float currenttime = gEnv->pTimer->GetCurrTime();
		if ( m_pFileTexMips->m_fMipResidencyTime == 0.0f )
			m_pFileTexMips->m_fMipResidencyTime = currenttime;

		if ( (currenttime - m_pFileTexMips->m_fMipResidencyTime) < CRenderer::CV_r_texturesstreamingResidencyTimeTestLimit )
		{
			m_pFileTexMips->m_fMipResidencyFactor = max(0.0f, m_pFileTexMips->m_fMipResidencyFactor - 0.1f);
			if ( m_pFileTexMips->m_fMipResidencyFactor < CRenderer::CV_r_texturesstreamingResidencyThrottle )
			{
				m_pFileTexMips->m_eStreamingInfoFlags |= STexStreamingInfo::eStreamingInfo_Resident;
				m_pFileTexMips->m_fMipResidencyTime = currenttime;
				if (CRenderer::CV_r_TexturesStreamingDebug == 2)
					iLog->Log("New Resident Texture ('%s')", m_SrcName.c_str());
			}
		}
		else
		{
			m_pFileTexMips->m_fMipResidencyTime = 0.0f;
			m_pFileTexMips->m_fMipResidencyFactor = 1.0f;
		}
	}

	if (m_pFileTexMips->m_eStreamingInfoFlags&STexStreamingInfo::eStreamingInfo_Resident)
	{
		if ( (gEnv->pTimer->GetCurrTime() - m_pFileTexMips->m_fMipResidencyTime) > CRenderer::CV_r_texturesstreamingResidencyTime )
		{
			m_pFileTexMips->m_fMipResidencyFactor = 1.0f;
			m_pFileTexMips->m_fMipResidencyTime = 0.0f;
			m_pFileTexMips->m_eStreamingInfoFlags &= ~STexStreamingInfo::eStreamingInfo_Resident;
			if (CRenderer::CV_r_TexturesStreamingDebug == 2)
				iLog->Log("Removing Residency on Texture ('%s')", m_SrcName.c_str());
		}
	}

	if (CRenderer::CV_r_TexturesStreamingDebug == 2)
		iLog->Log("Updating mips: %s - Current: %i, Previous: %i", m_SrcName.c_str(), m_pFileTexMips->m_nMinMipCur, nNewMip);

	// update required mip for streaming
	m_pFileTexMips->m_nMinMipCur = nNewMip;
	m_pFileTexMips->m_nMinMipCurSigned = nMipIdSigned;

	// update high priority flag
	m_pFileTexMips->m_eStreamingInfoFlags |= bHighPriority ? STexStreamingInfo::eStreamingInfo_HighPriority : 0;

	StreamEnqueueForStreaming();
}

const bool CTexture::IsParticularMipStreamed(float fMipFactor) const
{
	if(!IsStreamed())
		return true;

	bool bHighPriority = false;
	if (m_pFileTexMips)
		bHighPriority = (m_pFileTexMips->m_eStreamingInfoFlags & STexStreamingInfo::eStreamingInfo_HighPriority) > 0;
	int nMipClamp = (s_bStreamingFromHDD || bHighPriority) ? 0 : CRenderer::CV_r_TexturesStreamingMipClampDVD;
	const int nMip = max(nMipClamp, CTexture::StreamCalculateMipsSigned(fMipFactor));
	return m_nMinMipVidUploaded <= nMip;
}

void CTexture::PrecacheAsynchronously( float fMipFactor, int nFlags, int nUpdateId, int nCounter)
{
	if (!IsStreamed())
		return;		// already done

	StreamUpdateMip(fMipFactor, nFlags, nUpdateId, nCounter);

	// for distance streaming it's just the same as update rendering distance
	StreamLoadFromCache(nFlags);
}

void CTexture::StreamLoadFromCache(const int Flags)
{
	if(!m_bStreamPrepared)
	{
		if(!StreamPrepare())
		{
			// ignore error for optional attached alpha channel
			if(!m_bNoTexture && !(m_nFlags&FT_ALPHA) && (m_nFlags&FT_FROMIMAGE))
			{
				bool bRes = Reload();
				assert(bRes);
			}
			return;
		}
	}

	// Synchronous loading if required
	if ((m_nMips == 1 || CRenderer::CV_r_texturesstreamingsync || (Flags & FPR_SYNCRONOUS)) && !m_bStreamingInProgress)
	{
		if(m_pFileTexMips->m_nMinMipCur < m_nMinMipVidUploaded)
			StreamLoadSynchronously(m_pFileTexMips->m_nMinMipCur, m_nMips - 1);
		assert(m_pDevTexture);
	}
}

void CTexture::StreamEnqueueForStreaming()
{
	if (!m_bStreamPrepared || !m_pFileTexMips || !IsStreamed())
		return;

	// Asynchronous request
	if (!m_bStreamRequested && !m_bStreamingInProgress && m_bStreamPrepared && !m_bPostponed && IsPartiallyLoaded() && m_pFileTexMips->m_nMinMipCur < m_nMinMipVidUploaded)
	{
		m_bStreamRequested = true;
		s_StreamingRequested.push_back(this);
#if !defined (_RELEASE) || defined(ENABLE_STATOSCOPE_RELEASE)
		CTexture::s_StreamingRequestsCount += 1;
#endif
		Relink();
	}
	else if(m_bStreamRequested && m_pFileTexMips->m_nMinMipCur >= m_nMinMipVidUploaded && !m_bStreamingInProgress)
	{
		bool bFound = false;
		for (uint32 i=0; i<s_StreamingRequested.size(); i++)
		{
			if (s_StreamingRequested[i] == this)
			{
				s_StreamingRequested.erase(s_StreamingRequested.begin() + i);
				bFound = true;
				break;
			}
		}
		assert(bFound);
		m_bStreamRequested = false;
	}
	// check if we still need it and if not, then abort it
	else if(m_bStreamingInProgress && m_pFileTexMips->m_nMinMipCur >= m_nMinMipVidUploaded)
	{
		AbortStreamingTasks(this);
	}
}

bool CTexture::StreamPrepare()
{
	CHK_MAINORRENDTH;

  if (!CRenderer::CV_r_texturesstreaming || m_nFlags & FT_DONT_STREAM)
    return false;

	LOADING_TIME_PROFILE_SECTION(iSystem);
	PROFILE_FRAME(Texture_StreamPrepare);

#ifndef _RELEASE
	CRY_DEFINE_ASSET_SCOPE( "Texture",m_sAssetScopeName );
#endif

	// release the old texture
  if (m_pDevTexture)
	  ReleaseDeviceTexture(false);

	if(!m_pFileTexMips)
	{
		const char *szExt = fpGetExtension(m_SrcName.c_str());
		if (szExt!=0 && !stricmp(szExt, ".tif") && !gEnv->pCryPak->IsFileExist(m_SrcName.c_str()))
		{
			m_SrcName = PathUtil::ReplaceExtension(m_SrcName, "dds");
	#if !defined(XENON) && !defined(PS3)		// for consoles we have *.ctx textures
			if (!gEnv->pCryPak->IsFileExist(m_SrcName))
				return false;
	#endif
		}

		if ((m_nFlags & FT_TEX_NORMAL_MAP) && !CryStringUtils::stristr(m_SrcName.c_str(), "_ddn"))
			FileWarning( m_SrcName.c_str(),"Normal map should have '_ddn' suffix in filename");

		_smart_ptr<CImageFile>  pIM(CImageFile::mfLoad_file(m_SrcName, ((m_nFlags & FT_ALPHA) ? FIM_ALPHA : 0) | FIM_STREAM_PREPARE));
		// Can't stream volume textures and textures without mips
		if (pIM == NULL || pIM->mfGetFormat() == eTF_Unknown || pIM->mfGet_depth() > 1 || pIM->mfGet_numMips() < 2)
			return false;

		STexCacheFileHeader& fh = m_CacheFileHeader;
		if(pIM->mfGet_Flags() & FIM_SPLITTED)
			m_nFlags |= FT_SPLITTED;
		if(pIM->mfGet_Flags() & FIM_X360_NOT_PRETILED)
			m_nFlags |= FT_TEX_WAS_NOT_PRE_TILED;
		if (pIM->mfGet_Flags() & FIM_ALPHA)
			m_nFlags |= FT_HAS_ATTACHED_ALPHA;			// if the image has alpha attached we store this in the CTexture

		// copy image properties
		m_nWidth = pIM->mfGet_width();
		m_nHeight = pIM->mfGet_height();
		m_nDepth = pIM->mfGet_depth();
		m_nFlags |= FT_FROMIMAGE;
		m_bUseDecalBorderCol = (pIM->mfGet_Flags() & FIM_DECAL) != 0;
		m_bIsSRGB = (pIM->mfGet_Flags() & FIM_SRGB_READ) != 0;
#	if defined(DIRECT3D10) || defined( XENON )
		m_bIsSRGB &= gRenDev->IsLinearSpaceShadingEnabled();
#	endif
		m_nMips = pIM->mfGet_numMips();
		m_eTFSrc = pIM->mfGetSrcFormat();
		m_eTFDst = pIM->mfGetFormat();
		m_AvgColor = pIM->mfGet_AvgColor();
		m_SrcName = pIM->mfGet_filename();
		m_eTT = pIM->mfGet_NumSides() == 1 ? eTT_2D : eTT_Cube;
		fh.m_nSides = m_eTT != eTT_Cube ? 1 : 6;
		SAFE_DELETE(m_pFileTexMips);
		m_pFileTexMips = new STexStreamingInfo(m_nMips);
		m_nStreamingPriority = 0;
		m_nMinMipVidUploaded = MAX_MIP_LEVELS;
		m_fCurrentMipBias = 0.0f;

		// allocate and fill up streaming auxiliary structures
		for(int iMip = 0;iMip < m_nMips;++iMip)
		{
			m_pFileTexMips->m_nNumSides = fh.m_nSides;
			m_pFileTexMips->m_pMipHeader[iMip].m_Mips = new SMipData[m_pFileTexMips->m_nNumSides];
		}
		for(int iSide = 0;iSide < fh.m_nSides;++iSide)
		{
			int wdt = m_nWidth;
			int hgt = m_nHeight;
			for (int iMip=0; iMip<m_nMips; iMip++)
			{
				assert(wdt && hgt);
				m_pFileTexMips->m_pMipHeader[iMip].m_USize = wdt;
				m_pFileTexMips->m_pMipHeader[iMip].m_VSize = hgt;
				if (CTexture::IsDXTCompressed(m_eTFSrc))
				{
					m_pFileTexMips->m_pMipHeader[iMip].m_USize = (m_pFileTexMips->m_pMipHeader[iMip].m_USize + 3) & ~3;
					m_pFileTexMips->m_pMipHeader[iMip].m_VSize = (m_pFileTexMips->m_pMipHeader[iMip].m_VSize + 3) & ~3;
				}
				m_pFileTexMips->m_pMipHeader[iMip].m_SideSize = TextureDataSize(max(1, m_pFileTexMips->m_pMipHeader[0].m_USize >> iMip), max(1, m_pFileTexMips->m_pMipHeader[0].m_VSize >> iMip), 1, 1, m_eTFDst);
				wdt = max(1, wdt >> 1);
				hgt = max(1, hgt >> 1);
			}
		}

		// fill file seeks
		int nSeek = pIM->mfGet_StartSeek();
		for(int iSide = 0;iSide < fh.m_nSides;++iSide)
		{
			int wdt = m_nWidth;
			int hgt = m_nHeight;
			for (int iMip=0; iMip<m_nMips;++iMip)
			{
				m_pFileTexMips->m_pMipHeader[iMip].m_Mips[iSide].m_Seek = nSeek;
				nSeek += CTexture::TextureDataSize(wdt, hgt, 1, 1, m_eTFSrc);
				wdt = max(1, wdt >> 1);
				hgt = max(1, hgt >> 1);
			}
		}

		for (int iMip=0; iMip<m_nMips; iMip++)
		{
			m_pFileTexMips->m_pMipHeader[iMip].m_SideSizeWithMips = 0;
			for (int j=iMip; j<m_nMips; j++)
				m_pFileTexMips->m_pMipHeader[iMip].m_SideSizeWithMips += m_pFileTexMips->m_pMipHeader[j].m_SideSize;
		}

		// set up pixel format and check if it's supported for 
		ClosestFormatSupported(m_eTFDst);
		if(!m_pPixelFormat && !DDSFormats::IsNormalMap(m_eTFDst)) // special case for 3DC and CTX1
		{
			assert(0);
			gEnv->pLog->LogError("Failed to load texture %s': format '%s' is not supported", NameForTextureFormat(m_eTFDst), m_SrcName.c_str());
			return false;
		}
		if(m_pPixelFormat)
			m_bIsSRGB &= m_pPixelFormat->bCanReadSRGB;
		else
			m_bIsSRGB = false;

		// calc num persistent mips and check if it makes sense to stream this texture
		{
			fh.m_nMipsPersistent = DDSSplitted::GetNumLastMips(m_nWidth, m_nHeight, m_nMips, m_CacheFileHeader.m_nSides, m_eTFSrc, (m_nFlags & FT_ALPHA) ? FIM_ALPHA : 0);
//#ifndef XENON
			if((m_nWidth <= DDSSplitted::etexLowerMipMaxSize || m_nHeight <= DDSSplitted::etexLowerMipMaxSize)
				|| m_nMips <= fh.m_nMipsPersistent || fh.m_nMipsPersistent == 0)
			{
				// texture is too small to stream it, mark as not streamable
				SAFE_DELETE(m_pFileTexMips);
				m_nFlags |= FT_DONT_STREAM;
				m_bStreamed = false;
				m_bStreamPrepared = false;
				SetWasUnload(false);
				m_bStreamingInProgress = false;
				m_bStreamRequested = false;
				m_bNoTexture = false;
				return false;
			}
//#endif
		}

		assert (m_eTFDst != eTF_Unknown);

		Relink();

		SetTexStates();
		PostCreate();


		// Always load lowest nMipsPersistent mips synchronously
		byte *buf = NULL;
		if (m_nMips > 1)
		{
			int nSyncStartMip = -1;
			int nSyncEndMip = -1;
			int nStartLowestM = max(0, m_nMips - m_CacheFileHeader.m_nMipsPersistent);
			if(nStartLowestM < m_nMinMipVidUploaded)
			{
				nSyncStartMip = nStartLowestM;
				nSyncEndMip = m_nMips - 1;
			}

			int nOffs = 0;
			assert(nSyncStartMip <= nSyncEndMip);

			for (int iSide=0; iSide<m_CacheFileHeader.m_nSides; iSide++)
			{
				nOffs = 0;
				for (int iMip=nSyncStartMip; iMip<=nSyncEndMip; iMip++)
				{
					STexStreamingInfo::SMipHeader& mh = m_pFileTexMips->m_pMipHeader[iMip];
					SMipData *mp = &mh.m_Mips[iSide];
					if(!mp->DataArray)
						mp->Init(mh.m_SideSize, mh.m_USize, mh.m_VSize);




					CTexture::s_nTexturesDataBytesLoaded += mh.m_SideSize;
					if (iSide == 0 || (m_nFlags & FT_REPLICATE_TO_ALL_SIDES) == 0)
					{
						assert(pIM->mfIs_image(iSide));
						buf = pIM->mfGet_image(iSide);
						cryMemcpy(&mp->DataArray[0], &buf[nOffs], mh.m_SideSize);
						nOffs += mh.m_SideSize;
					}
					else if (iSide > 0)
					{
						if (m_nFlags & FT_REPLICATE_TO_ALL_SIDES)
							memcpy(&mp->DataArray[0], &mh.m_Mips[0].DataArray[0], mh.m_SideSize);
						else
							assert(0);
					}
					else
						assert(0);
				}
			}
		}
	}
	else
	{
		assert(m_pFileTexMips);
		assert(m_pFileTexMips);
		for (int iSide=0; iSide<m_CacheFileHeader.m_nSides; ++iSide)
		{
			for (int iMip=m_nMips - m_CacheFileHeader.m_nMipsPersistent; iMip<m_nMips; iMip++)
			{
				STexStreamingInfo::SMipHeader& mh = m_pFileTexMips->m_pMipHeader[iMip];
				SMipData *mp = &mh.m_Mips[iSide];
				assert(mp->DataArray);
			}
		}
	}




















	// make sure this happens on the D3D Thread!!
 #if ((defined(WIN32) && defined(WIN64)) || defined(DIRECT3D10))
	gRenDev->m_pRT->RC_CopyDataToTexture(this, m_nMips - m_CacheFileHeader.m_nMipsPersistent, m_nMips - 1);
  SetWasUnload(false);
 #else
  m_nMinMipVidUploaded = m_nMips;
  SetWasUnload(true);
 #endif

	StreamUpdateMip(100000.f, 0, -1, 0);	// update current mip

	for(int z=0; z<MAX_PREDICTION_ZONES; z++)
		m_pFileTexMips->m_arrSPInfo[z].nRoundUpdateId = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_arrZonesRoundId[z];
	m_bStreamPrepared = true;
	m_bPostponed = false;
	m_fCurrentMipBias = 0.f;

  // store file position on DVD
  const string sLastChunkName = DDSSplitted::MakeName(m_SrcName.c_str(), 0, FIM_SPLITTED | ((GetFlags() & FT_ALPHA) ? FIM_ALPHA : 0));
  m_pFileTexMips->m_nFileDiskOffsetKB = (uint32)(gEnv->pCryPak->GetFileOffsetOnMedia(sLastChunkName) / 1024);






  return true;
}

//=========================================================================
void CTexture::StreamValidateTexSize()
{
	return;

#if defined(_DEBUG)
	CHK_MAINORRENDTH;

	// check if all textures in the list are valid and streamable
	static size_t j=0;
	if(j>=s_DistanceSortedTextures.size())
		j=0;
	for(size_t nIter=0;nIter<10&&j<s_DistanceSortedTextures.size();++j,++nIter)
	{
		CTexture* tp = s_DistanceSortedTextures[j];
		if(tp != NULL)
		{
			assert(tp->IsValid());
			assert(tp->IsStreamed());
		}
	}

	int nSizeManagedStreamed = 0;
  int nSizeManagedNonStreamed = 0;
  int nSizeDynamic = 0;
  static SResourceContainer *pRL = CBaseResource::GetResourcesForClass(CTexture::mfGetClassName());
  ResourcesMapItor itor;
  if (!pRL)
    return;

	// do this check once per 120 frames
	if(gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID%120 != 0)
		return;

  for (itor=pRL->m_RMap.begin(); itor!=pRL->m_RMap.end(); itor++)
  {
    CTexture *tp = (CTexture *)itor->second;
    if (!tp->IsDynamic())
    {
      if (tp->IsStreamed() && tp->m_bStreamPrepared)
			{
				assert(tp->m_pFileTexMips);
				if(tp->m_pFileTexMips->m_pPoolItem)
				{
					if(tp->m_pFileTexMips->m_pPoolItem)
						nSizeManagedStreamed += tp->m_pFileTexMips->m_pPoolItem->m_pOwner->m_Size;
				}
				else
					nSizeManagedStreamed += tp->GetDeviceDataSize();
			}
      else
        nSizeManagedNonStreamed += tp->GetDeviceDataSize();
    }
    else
      nSizeDynamic += tp->GetDeviceDataSize();
  }
  STexPoolItem *pIT = CTexture::s_FreeTexPoolItems.m_PrevFree;
  while (pIT != &CTexture::s_FreeTexPoolItems)
  {
    nSizeManagedStreamed += pIT->m_pOwner->m_Size;
    pIT = pIT->m_PrevFree;
  }

//  assert(nSizeManagedStreamed == s_nStatsCurManagedStreamedTexMem);
//  assert(nSizeManagedNonStreamed == s_nStatsCurManagedNonStreamedTexMem);
//  assert(nSizeDynamic == s_nStatsCurDynamicTexMem);
#endif
}

void CTexture::AsyncRequestsProcessing()
{
	CHK_RENDTH;

	CTexture::StreamUpdatePool();

	CTexture *pTex = 0;

	PROFILE_FRAME(Texture_StreamASyncRequests);

	const int nPoolSize = CRenderer::GetTexturesstreampoolsize()*1024*1024;
	int nCurrentPoolSize = CTexture::s_nStatsCurManagedStreamedTexMem;

	int nCurSizeToLoad = 0;

	int nMaxRequestedBytes = (int)(CRenderer::CV_r_TexturesStreamingMaxRequestedMB * 1024.f * 1024.f);
	int nMaxRequestedJobs = CRenderer::CV_r_TexturesStreamingMaxRequestedJobs;

	if(gRenDev->m_nFlushAllPendingTextureStreamingJobs && s_StreamingRequested.size() && nMaxRequestedBytes && nMaxRequestedJobs)
	{
		nMaxRequestedBytes = 1024*1024*1024;
		nMaxRequestedJobs = 1024*1024;
		gRenDev->m_nFlushAllPendingTextureStreamingJobs--;
	}

	s_bStreamingFromHDD = iSystem->GetStreamEngine()->IsStreamDataOnHDD();

	if (s_StreamingRequested.size() && s_StreamingTextures.size() < nMaxRequestedJobs && s_nBytesSubmittedToStreaming < nMaxRequestedBytes)
	{
		CompareStreamingPrioriry sort_op(gRenDev->m_RP.m_TI[gRenDev->m_pRT->GetThreadList()].m_arrZonesRoundId, gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID);

    std::sort(&s_StreamingRequested[0], &s_StreamingRequested[0]+s_StreamingRequested.size(), sort_op);
		const int posponeThresholdKB = (CRenderer::CV_r_texturesstreamingPostponeMips && !s_bStreamingFromHDD) ? (CRenderer::CV_r_texturesstreamingPostponeThresholdKB*1024) : INT_MAX;
		const int posponeThresholdMip = (CRenderer::CV_r_texturesstreamingPostponeMips) ? CRenderer::CV_r_texturesstreamingPostponeThresholdMip : 0;

		GetISystem()->GetStreamEngine()->BeginReadGroup(); // Make sure new streaming requests are accumulated

		uint32 iTex=0;
		for (; iTex<s_StreamingRequested.size(); iTex++)
    {
      pTex = s_StreamingRequested[iTex];
      if (!pTex)
        continue;

			assert (pTex->m_bStreamRequested);

			// async processing doesn't support concurrent texture uploads
			if (pTex->m_bStreamingInProgress || !pTex->m_bStreamPrepared)
				continue;

      const STexStreamingInfo::SMipHeader *mh = pTex->m_pFileTexMips->m_pMipHeader;
      assert (pTex->m_bStreamRequested);
      
			int nASyncStartMip = pTex->m_pFileTexMips->m_nMinMipCur;
			if (nASyncStartMip >= pTex->m_nMinMipVidUploaded)
			{
				// No mip levels to load - Don't need texture anymore
				iTex = pTex->RemoveRequest( iTex );
				continue;
			}
			const int nSides = pTex->m_CacheFileHeader.m_nSides;
      const int nMips = pTex->m_nMips;
      const int nASyncEndMip = pTex->m_nMinMipVidUploaded-1;

			// limit amount of requests
			if (s_nBytesSubmittedToStreaming > nMaxRequestedBytes || s_StreamingTextures.size() > nMaxRequestedJobs)
        break;

			// check if we're out of pool size
			if (nCurrentPoolSize + nCurSizeToLoad >= nPoolSize)
			{
				// trigger pool overflow
				s_bPoolOverflow = 1;
				break;
			}
			nCurrentPoolSize -= nCurSizeToLoad;

			// postpone/discard higher mips if necessary
			bool postponeHigherMips = false;
			const int nMipSizeLargest = CTexture::TextureDataSize(mh[nASyncStartMip].m_USize, mh[nASyncStartMip].m_VSize, 1, 1, pTex->GetSrcFormat()) * ((pTex->m_nFlags & FT_REPLICATE_TO_ALL_SIDES) != 0 ? 1 : nSides);
			if (nMipSizeLargest >= posponeThresholdKB || posponeThresholdMip > nASyncStartMip)
			{
				if(nASyncStartMip < nASyncEndMip)
				{
					postponeHigherMips = true;
					nASyncStartMip++;
				}
			}

			int nSizeToLoad = 0;
			for (int i=nASyncStartMip; i<=nASyncEndMip; i++)
				nSizeToLoad += CTexture::TextureDataSize(mh[i].m_USize, mh[i].m_VSize, 1, 1, pTex->GetSrcFormat()) * ((pTex->m_nFlags & FT_REPLICATE_TO_ALL_SIDES) != 0 ? 1 : nSides);

			// add more mips in order to achieve the required granularity
			// (but never add upto the highest mip because this one is stored in a separate pak and would break the granularity again)
			while(nSizeToLoad < CRenderer::CV_r_texturesstreamingMinReadSizeKB * 1024 && 
				nASyncStartMip > max(posponeThresholdMip,(int)(CRenderer::CV_r_TexturesStreamingMipBias + gRenDev->m_fTexturesStreamingGlobalMipFactor)))
			{
				nASyncStartMip--;			// add one more mip
				nSizeToLoad += CTexture::TextureDataSize(mh[nASyncStartMip].m_USize, mh[nASyncStartMip].m_VSize, 1, 1, pTex->GetSrcFormat()) * ((pTex->m_nFlags & FT_REPLICATE_TO_ALL_SIDES) != 0 ? 1 : nSides);
			}

			if (nSizeToLoad)
			{
				bool requestStreaming = true;
				STexPoolItem *pNewPoolItem = NULL;










				if(requestStreaming)
				{
					if (StartStreaming(pTex, pNewPoolItem, mh[nASyncStartMip].m_Mips[0].m_Seek, nSizeToLoad, nASyncStartMip, nASyncEndMip))
					//if (!postponeHigherMips || bIsStreamingFromDVD)
					{
						iTex = pTex->RemoveRequest( iTex );
					} else {
						break;
					}

#ifndef _RELEASE
					CTexture::s_nTexturesDataBytesLoaded += nSizeToLoad;
#	ifdef DO_RENDERLOG
					if (gRenDev->m_LogFileStr)
						gRenDev->LogStrv(SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID], "Async Start Load '%s' (Mips: %d-%d[%d]), Size: %d, Time: %.3f\n", pTex->m_SrcName.c_str(), nASyncStartMip, nASyncEndMip, nMips, nSizeToLoad, iTimer->GetAsyncCurTime());
#	endif
					if (CRenderer::CV_r_TexturesStreamingDebug == 2)
						iLog->Log("Requesting mips: %s - From: %i, To: %i", pTex->m_SrcName.c_str(), nASyncStartMip, nASyncEndMip);
#endif
				}
      }
    }
		GetISystem()->GetStreamEngine()->EndReadGroup(); // Make sure new streaming requests are accumulated

#if !defined(_RELEASE) || defined(ENABLE_STATOSCOPE_RELEASE)
		// refresh statistics
		s_nBytesRequiredNotSubmitted = 0;
		for (; iTex<s_StreamingRequested.size(); iTex++)
		{
			pTex = s_StreamingRequested[iTex];
			if (!pTex) continue;
			const STexStreamingInfo::SMipHeader *mh = pTex->m_pFileTexMips->m_pMipHeader;
			if (!mh) continue;
			const int nSides = pTex->m_CacheFileHeader.m_nSides;
			const int nMips = pTex->m_nMips;
			const int nASyncEndMip = pTex->m_nMinMipVidUploaded-1;
			const int nASyncStartMip = pTex->m_pFileTexMips->m_nMinMipCur;
			if (nASyncStartMip >= nASyncEndMip) continue;
			s_nBytesRequiredNotSubmitted += CTexture::TextureDataSize(mh[nASyncStartMip].m_USize, mh[nASyncStartMip].m_VSize, nASyncEndMip - nASyncStartMip + 1, 1, pTex->GetSrcFormat())\
																						* ((pTex->m_nFlags & FT_REPLICATE_TO_ALL_SIDES) != 0 ? 1 : nSides);
		}

		if (pTex && (pTex->m_nAccessFrameID - pTex->m_nUpdateFrameID) >= 0 && (pTex->m_nAccessFrameID - pTex->m_nUpdateFrameID) < 2)
		{
			CTexture::s_TexturesUpdatedRendered += 1;
		}
		CTexture::StreamValidateTexSize();
#endif
  }
}

bool CTexture::StartStreaming(CTexture* pTex, STexPoolItem* pNewPoolItem, const size_t nOffset, const size_t nSize, const int nStartMip, const int nEndMip)
{
	CHK_RENDTH;

	//assert(nEndMip < max(0, pTex->m_nMips - pTex->m_CacheFileHeader.m_nMipsPersistent));

	DDSSplitted::Chunks chunks;

	uint32 nImageFlags = 0;
	if(pTex->m_nFlags&FT_SPLITTED)
		nImageFlags |= FIM_SPLITTED;
	if(pTex->m_nFlags&FT_ALPHA)
		nImageFlags |= FIM_ALPHA;

	DDSSplitted::GetFilesToRead(chunks, pTex->m_SrcName, nOffset, nSize, pTex->m_nWidth, pTex->m_nHeight, 
															pTex->m_nDepth, pTex->m_CacheFileHeader.m_nSides, pTex->m_nMips, nStartMip, 
															nEndMip, pTex->m_CacheFileHeader.m_nMipsPersistent, pTex->GetSrcFormat(), 
															nImageFlags);
	assert(chunks.size() == nEndMip - nStartMip + 1);

	bool bSuccesfullRequested = false;

	STexStreamAsyncSharedInfo *pTexStreamingSharedInfo = new STexStreamAsyncSharedInfo;
	pTexStreamingSharedInfo->m_pTexture = pTex;
	pTexStreamingSharedInfo->m_pNewPoolItem =	pNewPoolItem;
#ifndef _RELEASE
	pTexStreamingSharedInfo->m_fStartTime = iTimer->GetAsyncTime().GetSeconds();
#endif
	pTexStreamingSharedInfo->m_nAsyncRefCount = chunks.size();
	pTexStreamingSharedInfo->m_nSyncRefCount = chunks.size();
	pTexStreamingSharedInfo->m_nHigherUploadedMip = nStartMip;
	pTexStreamingSharedInfo->m_nLowerUploadedMip = nEndMip;

	// update streaming frame ID
	pTex->m_pFileTexMips->m_nStreamingSwitchUpdateId = pTex->m_pFileTexMips->m_arrSPInfo[MAX_PREDICTION_ZONES-1].nRoundUpdateId;

	// add requests
	size_t nBufferOffset = 0;
	for(DDSSplitted::Chunks::reverse_iterator it = chunks.rbegin();it != chunks.rend();++it)
	{
		STexAsyncStreamingInfo* pTexStreamingInfo = new STexAsyncStreamingInfo;
		assert(it->m_nMipLevel >= nStartMip);
		pTexStreamingInfo->m_nRelativeMipLevel = it->m_nMipLevel - nStartMip;
		pTexStreamingInfo->m_pSharedInfo = pTexStreamingSharedInfo;

		const DDSSplitted::ChunkInfo& chunk = *it;

		StreamReadParams StrParams;
		StrParams.nFlags = 0;
		StrParams.dwUserData = (DWORD_PTR)pTexStreamingInfo;
		StrParams.nLoadTime = 1;
		StrParams.nMaxLoadTime = 4;
		StrParams.ePriority = pTex->m_pFileTexMips->m_eStreamingInfoFlags & STexStreamingInfo::eStreamingInfo_HighPriority ? estpUrgent : estpNormal;
//		StrParams.nPriority = (int)(1000000.f / max(.1f, pTex->m_pFileTexMips->m_fLastMipFactor)) + (nSize + nBufferOffset) / 1024;	// distance + buffer m_nOffsetInFile
		StrParams.pBuffer = NULL;
		StrParams.nOffset = chunk.m_nOffsetInFile;
		StrParams.nSize = chunk.size;
		
		if (CTexture::StreamAddTask(pTex, chunk.fileName.c_str(), &pTexStreamingInfo->m_Callback, &StrParams)) {
			bSuccesfullRequested = true;
		} else {
			delete pTexStreamingInfo;
			break;
		}

		nBufferOffset += chunk.size;
		CryInterlockedAdd(s_nBytesSubmittedToStreaming.Addr(), (int)chunk.size);
	}
	assert(nBufferOffset == nSize);

	if (!bSuccesfullRequested)
		delete pTexStreamingSharedInfo;

	return bSuccesfullRequested;
}

bool CTexture::StreamAddTask( CTexture* pTex, const char* szFile, IStreamCallback* pCallback /*= NULL*/, StreamReadParams* pParams /*= NULL*/ )
{
	IReadStreamPtr task = iSystem->GetStreamEngine()->StartRead(eStreamTaskTypeTexture, szFile, pCallback, pParams);

	if (task)
	{
		if(pTex)
			pTex->SetStreamingInProgress(true);
    ScopedSwitchToGlobalHeap useGlobalHeap;
		s_StreamingTextures.push(task);

		return true;
	}

	return false;
}

void CTexture::StreamRemoveTask( CTexture* pTex, IReadStreamPtr pTask )
{
	CHK_MAINORRENDTH;
	if(pTex)
	{
		assert(pTex->m_bStreamingInProgress);
	}
	bool res = s_StreamingTextures.try_remove(pTask);
}

void CTexture::StreamUploadMips(int nStartMip, int nEndMip, STexPoolItem* pNewPoolItem)
{
	CTimeValue time0 = iTimer->GetAsyncTime();

















	// make sure this happens on the D3D Thread for DirectX9 at least!!
	gRenDev->m_pRT->RC_CopyDataToTexture(this, nStartMip, nEndMip);
	//StreamCopyMips(nStartMip, nEndMip, true);


  if (s_bStreamDontKeepSystem)
    StreamReleaseMipsData(0, m_nMips - 1);

  gRenDev->m_RP.m_PS[gRenDev->m_RP.m_nProcessThreadID].m_fTexUploadTime += (iTimer->GetAsyncTime() - time0).GetSeconds();
}

void CTexture::InitStreaming()
{
	CHK_MAINORRENDTH;

	iLog->Log("Init textures management (%d Mb of video memory is available)...", (int32)(gRenDev->m_MaxTextureMemory/1024/1024));

	// reset all statistics
	s_nStreamingThroughput = 0;
	s_nStreamingTotalTime = 0;

  SSystemGlobalEnvironment *pEnv = iSystem->GetGlobalEnvironment();

	// auto mode
#if !defined(XENON) && !defined(PS3) && !defined(_RELEASE)
  if (CRenderer::CV_r_texturesstreaming == 2)
  {
    // or we have enough memory resources to handle
    if (sizeof(void*) == 8 && gRenDev->m_MaxTextureMemory >= 500*1024*1024)
      CRenderer::CV_r_texturesstreaming = 0;
  }
#endif

  if (!s_FreeTexPoolItems.m_NextFree)
  {
    s_FreeTexPoolItems.m_NextFree = &s_FreeTexPoolItems;
    s_FreeTexPoolItems.m_PrevFree = &s_FreeTexPoolItems;
  }
#if !defined(XENON) && !defined(PS3)
	if (CRenderer::CV_r_texturesstreaming)
  {
#ifdef WIN32
    // no 64 bits
    if (!pEnv->pi.win64Bit)
			CRenderer::CV_r_texturesstreampoolsize = min(128, CRenderer::CV_r_texturesstreampoolsize);
    else
#endif
    if (gRenDev->m_MaxTextureMemory <= 220*1024*1024)
			CRenderer::CV_r_texturesstreampoolsize = min(100, CRenderer::CV_r_texturesstreampoolsize);
    else
    if (gRenDev->m_MaxTextureMemory <= 500*1024*1024)
			CRenderer::CV_r_texturesstreampoolsize = min(148, CRenderer::CV_r_texturesstreampoolsize);
    else
    if (gRenDev->m_MaxTextureMemory <= 700*1024*1024)
			CRenderer::CV_r_texturesstreampoolsize = min(256, CRenderer::CV_r_texturesstreampoolsize);
    else
			CRenderer::CV_r_texturesstreampoolsize = max(256, CRenderer::CV_r_texturesstreampoolsize);
		CRenderer::CV_r_texturesstreampoolsize = max(100, CRenderer::CV_r_texturesstreampoolsize);
  }
#endif

  s_nStreamingMode = CRenderer::CV_r_texturesstreaming;

#if !defined(XENON) && !defined(PS3)
  s_bStreamDontKeepSystem = CRenderer::CV_r_texturesstreamingonlyvideo == 0;


#endif

	if (CRenderer::CV_r_texturesstreaming)
	{
		iLog->Log("  Enabling of textures streaming...");
		iLog->Log("  Using %d Mb of textures pool for streaming...", CRenderer::GetTexturesstreampoolsize());
	}
	else
		iLog->Log("  Disabling of textures streaming...");

  if (gRenDev->m_MaxTextureMemory <= 256*1024*1024)
  {
    SDynTexture::s_CurDynTexAtlasCloudsMaxsize = min(24u, SDynTexture::s_CurDynTexAtlasCloudsMaxsize);
    SDynTexture::s_CurDynTexAtlasSpritesMaxsize = min(32u, SDynTexture::s_CurDynTexAtlasSpritesMaxsize);
    //SDynTexture::s_CurTexAtlasSize = min(128u, SDynTexture::s_CurTexAtlasSize);
    //SDynTexture::s_CurDynTexMaxSize = min(128u, SDynTexture::s_CurDynTexMaxSize);
  }
  iLog->Log("  Video textures: Atlas clouds max size: %d Mb", SDynTexture::s_CurDynTexAtlasCloudsMaxsize);
  iLog->Log("  Video textures: Atlas sprites max size: %d Mb", SDynTexture::s_CurDynTexAtlasSpritesMaxsize);
  iLog->Log("  Video textures: Dynamic managed max size: %d Mb", SDynTexture::s_CurDynTexMaxSize);

	// re-init all textures
	iLog->Log("  Reloading all textures...");
	{
		AUTO_LOCK(CBaseResource::s_cResLock);
    SResourceContainer *pRL = CBaseResource::GetResourcesForClass(CTexture::mfGetClassName());
    if (pRL)
    {
      for (ResourcesMapItor itor=pRL->m_RMap.begin(); itor!=pRL->m_RMap.end(); itor++)
      {
        CTexture *tp = (CTexture *)itor->second;
        if (!tp) continue;
        tp->ToggleStreaming(CRenderer::CV_r_texturesstreaming != 0);
      }
    }
  }
	iLog->Log("  Finished reloading textures...");
	iLog->Log("  Finished initializing textures streaming...");

	if (gEnv->pLocalMemoryUsage)
		gEnv->pLocalMemoryUsage->DeleteGlobalData();
}

void CTexture::FlushStreaming()
{
	FlushAllStreamingTasks(true);

	// flush all pool items
	{
		AUTO_LOCK(STexPoolItem::s_sSyncLock);
		STexPoolItem *pIT = CTexture::s_FreeTexPoolItems.m_PrevFree;
		while (pIT != &CTexture::s_FreeTexPoolItems)
		{
			assert(!pIT->m_pTex);
			STexPoolItem *pITNext = pIT->m_PrevFree;
			delete pIT;
			pIT = pITNext;
		}
	}
}

void CTexture::FlushAllStreamingTasks(const bool bAbort/* = false*/)
{
	// flush all tasks globally
	CHK_MAINORRENDTH;
	iLog->Log("Flushing pended textures...");
	CryMT::queue<IReadStreamPtr>::AutoLock lock(s_StreamingTextures.get_lock());
	while(!s_StreamingTextures.empty())
	{
		IReadStreamPtr pStream = NULL;
		if(s_StreamingTextures.try_pop(pStream) && pStream)
		{
			if(bAbort)
				pStream->Abort();
			else
				pStream->Wait();
		}
	}

	stl::free_container(s_StreamingTextures);
	stl::free_container(s_StreamingRequested);

	assert(s_nBytesSubmittedToStreaming == 0);
	iLog->Log("Finished flushing pended textures...");
}

void CTexture::AbortStreamingTasks( CTexture* pTex, bool isDestroying )
{
  CHK_MAINORRENDTH;
	std::vector<IReadStreamPtr> vecToAbort;
	{
		CryMT::queue<IReadStreamPtr>::AutoLock lock(s_StreamingTextures.get_lock());
		if(!pTex->m_bStreamingInProgress || s_StreamingTextures.empty())
			return;
		vecToAbort.reserve(5);
		IReadStreamPtr* pBegin = (IReadStreamPtr*)(&((IReadStreamPtr&)s_StreamingTextures.front()));
		for(int iTex = 0;iTex<s_StreamingTextures.size();++iTex)
		{
			IReadStreamPtr& pStream = *(pBegin + iTex);
			const bool bIsLodSwitch = pStream->GetParams().nSize == 0;
			STexAsyncUnloadInfo* pTexLodInfo = (STexAsyncUnloadInfo*)pStream->GetUserData();
			STexAsyncStreamingInfo* pTexStreamInfo = (STexAsyncStreamingInfo*)pStream->GetUserData();
			STexStreamAsyncSharedInfo* pTexStreamSharedInfo = pTexStreamInfo->m_pSharedInfo;
			CTexture *const tp = bIsLodSwitch ? pTexLodInfo->m_pTexture : pTexStreamSharedInfo->m_pTexture;
			if(tp == pTex)
			{
				if(!pStream->IsFinished() && !pStream->IsError())
				{
					pStream->AddRef();
					vecToAbort.push_back(pStream);
					const bool bRes = s_StreamingTextures.try_remove(pStream);
					assert(bRes);
					--iTex;
				}
				else
				{
					// If we get here the streaming is "finished" but the RC_StreamOnComplete
					// task is still in the render queue. AbortStreamingTasks is called on texture
					// destruction so we must NULL these pointers to prevent the RC_StreamOnComplete
					// job accessing a deleted texture
					if (isDestroying)
					{
						if (bIsLodSwitch)
							pTexLodInfo->m_pTexture=NULL;
						else
							pTexStreamSharedInfo->m_pTexture=NULL;
					}
					else
					{
						if (bIsLodSwitch)
							pTexLodInfo->m_bCancel = true;
						else
							pTexStreamSharedInfo->m_bCancel = true;
					}

					pTex->SetStreamingInProgress(false);
				}
			}
		}
	}
	for(size_t iTex = 0;iTex<vecToAbort.size();++iTex) {
		vecToAbort[iTex]->Abort();
		vecToAbort[iTex]->Release();	
	}
}

int CTexture::GetRequiredMipMemUsage()
{
	int nSizeToLoad = 0;
	std::vector<CTexture*>::iterator item = CTexture::s_DistanceSortedTextures.begin(), end = CTexture::s_DistanceSortedTextures.end();
	for (; item != end; ++item)
	{
		const int nSides = (*item)->m_CacheFileHeader.m_nSides;
		const STexStreamingInfo::SMipHeader *mh = (*item)->m_pFileTexMips->m_pMipHeader;

		if (!mh)
			continue;

		int nASyncStartMip = (*item)->m_pFileTexMips->m_nMinMipCur;
		int nASyncEndMip = (*item)->m_nMinMipVidUploaded;

		if ( nASyncStartMip != nASyncEndMip )
		{
			for (int i=nASyncStartMip; i<=nASyncEndMip; i++)
			{
				nSizeToLoad += CTexture::TextureDataSize(mh[i].m_USize, mh[i].m_VSize, 1, 1, (*item)->GetSrcFormat()) * (((*item)->m_nFlags & FT_REPLICATE_TO_ALL_SIDES) != 0 ? 1 : nSides);
			}
		}
	}
	return nSizeToLoad;
}
